import { CzmCzmModelPrimitive, CzmInstances, CzmViewer } from 'xbsj-xe2/dist-node/xe2-cesium-objects';
import { ESForest, newESJTreeParam } from '.';
import { CzmESGltfModel, CzmESVisualObject, defaultFlyToRotation, ESGltfModel, getCenterAndViewDistance, getFlyToCenterAndRotation } from 'esobjs-xe2-plugin/dist-node/esobjs-xe2-plugin-main';
import { Destroyable, track } from 'xbsj-xe2/dist-node/xe2-base-utils';
import { SceneObjectPickedInfo } from 'xbsj-xe2/dist-node/xe2-base-objects';
import * as Cesium from 'cesium';
import { toColor } from 'xbsj-renderer/dist-node/xr-cesium';
import { createProcessingFromAsyncFunc, step } from 'xbsj-renderer/dist-node/xr-utils';
export class CzmESForest<T extends ESForest = ESForest> extends CzmESVisualObject<T> {
    static readonly type = this.register<ESForest>(ESForest.type, this);
    private _modelArr: { [meshPath: string]: ESGltfModel } = {};
    private _allModelDestroyable: Destroyable[] = [];
    private _highLightFeature = [] as Cesium.ModelFeature[];

    constructor(sceneObject: T, czmViewer: CzmViewer) {
        super(sceneObject, czmViewer);
        const viewer = czmViewer.viewer;
        if (!viewer) {
            console.warn(`viewer is undefined!`);
            return;
        }
        const editModelProcess = this.createEditModelProcess();
        const removeAll = () => {
            this._allModelDestroyable.forEach((destroyable) => destroyable.destroy());
            this._allModelDestroyable = [];
            this._modelArr = {};
            this._highLightFeature = [];
        }
        this.ad(removeAll)
        {
            const update = (trees: newESJTreeParam[]) => {
                const tempJson = this.getTreeArr(trees);
                for (const meshPath in tempJson) {
                    if (Reflect.has(this._modelArr, meshPath)) {
                        this._modelArr[meshPath].instances = [
                            ...(this._modelArr[meshPath].instances ?? []),
                            ...tempJson[meshPath]
                        ];
                    } else {
                        this._allModelDestroyable.push(this.createModel(meshPath, tempJson[meshPath]));
                    }
                }
            }
            if (sceneObject.trees.length != 0) {
                const switchDate = sceneObject.formatData(sceneObject.trees, sceneObject.treeTypesAll);
                update(switchDate);
            }
            this.ad(sceneObject.addEvent.don(update))
        }
        this.ad(sceneObject.updateEvent.don((trees) => {
            const tempJson = this.getTreeArr(trees);
            for (const meshPath in tempJson) {
                if (!Reflect.has(this._modelArr, meshPath)) continue; // 不存在则跳过
                const instances = [...(this._modelArr[meshPath].instances ?? [])];
                if (instances.length == 0) continue;
                tempJson[meshPath].forEach(instance => {
                    const index = instances.findIndex(item => item.id == instance.id);
                    if (index != -1) {
                        // 更新
                        instances[index] = instance;
                    }
                })
                this._modelArr[meshPath].instances = [];
                this._modelArr[meshPath].instances = instances;
            }
        }))
        this.ad(sceneObject.removeEvent.don((treeIds) => {
            const treesIndex = this.getTreesIndex(treeIds);
            for (const meshPath in this._modelArr) {
                const instances = this._modelArr[meshPath].instances;
                this._modelArr[meshPath].instances = instances && instances.filter((item) => item.id ? !treesIndex.includes(item.id) : true);
            }
        }))
        this.ad(sceneObject.removeAllEvent.don(removeAll));
        // 砍倒直接删除
        this.ad(sceneObject.cutDownEvent.don((treeIds: string[]) => {
            sceneObject.remove(treeIds);
        }))
        this.ad(sceneObject.growthEvent.don((trees, timeLength) => {
            // 禁止dom元素响应鼠标事件
            // (viewer.container as HTMLDivElement).style.pointerEvents = 'none';
            // 获取时间戳
            let timestamp = Date.now();
            const step = 1000 / 2;
            let duration = 0; // 持续时间
            const growsInterval = trees.map(item => {
                const temp: any = { ...item }
                temp.scale = undefined;
                return temp;
            });
            const treesIndex = this.getTreesIndex(trees);
            const remove = viewer.scene.preRender.addEventListener(() => {
                const nowTime = Date.now();
                // 判定是否需要更新
                if (nowTime - timestamp < step) return;
                timestamp = nowTime;
                for (const meshPath in this._modelArr) {
                    const instances = [...(this._modelArr[meshPath].instances ?? [])];
                    trees.forEach((item, treeIndex) => {
                        const index = instances.findIndex(instance => instance.id == treesIndex[treeIndex]);
                        if (index != -1 && this._modelArr[meshPath].instances) {
                            if (!growsInterval[index].scale) {
                                const tempScale = this._modelArr[meshPath].instances[index].scale ?? [1, 1, 1];
                                growsInterval[index].scale = [
                                    item.scale[0] - tempScale[0],
                                    item.scale[1] - tempScale[1],
                                    item.scale[2] - tempScale[2]
                                ]
                            }
                            !instances[index].scale && (instances[index].scale = [1, 1, 1]);
                            instances[index].scale[0] += growsInterval[index].scale[0] / timeLength / 2;
                            instances[index].scale[1] += growsInterval[index].scale[1] / timeLength / 2;
                            instances[index].scale[2] += growsInterval[index].scale[2] / timeLength / 2;
                        }
                    })
                    this._modelArr[meshPath].instances = [];
                    this._modelArr[meshPath].instances = instances;
                }
                duration += step;
                if (duration >= 1000 * timeLength) {
                    remove();
                }
            });
        }))
        this.ad(sceneObject.rotateTreeEvent.don((treeId: string) => { editModelProcess.restart(undefined, treeId, "Rotation") }))
        this.ad(sceneObject.moveTreeEvent.don((treeId: string) => { editModelProcess.restart(undefined, treeId, "Position") }))
        {
            const update = (treeId: string | string[]) => {
                const index = this.getTreesIndex(Array.isArray(treeId) ? treeId : [treeId]);
                this.highLightModel(index)
            }
            this.ad(sceneObject.highlightTreeEvent.don(update))
        }
        this.ad(sceneObject.flyToTreeEvent.don((treeId: string,) => {
            const treesIndex = this.getTreesIndex([treeId]);
            for (const meshPath in this._modelArr) {
                const element = this._modelArr[meshPath];
                if (!element.instances) continue;
                const instance = element.instances.find(instance => instance.id === treesIndex[0]);
                if (instance) {
                    const center = instance.position;
                    const rotation = [...defaultFlyToRotation] as [number, number, number];
                    const { position: flyPosition, rotation: flyRotation } = getFlyToCenterAndRotation(center, rotation, 100);
                    czmViewer.flyTo(flyPosition, 0, flyRotation, 1000);
                    return;
                }
            }
        }))
    }
    override flyTo(duration: number | undefined, id: number): boolean {
        const { sceneObject, czmViewer, _modelArr } = this;
        if (!czmViewer.actived) return false;
        if (sceneObject.flyToParam || sceneObject.flyInParam) {
            return super.flyTo(duration, id);
        }
        const boundingSpheres = [];
        for (const meshPath in _modelArr) {
            const model = czmViewer.getCzmObject((czmViewer.getCzmObject(_modelArr[meshPath]) as CzmESGltfModel).model) as CzmCzmModelPrimitive;
            if (!model.primitive || !model.primitive.boundingSphere) continue;
            boundingSpheres.push(model.primitive.boundingSphere);
        }
        const boundingSphere = Cesium.BoundingSphere.fromBoundingSpheres(boundingSpheres);
        const cav = getCenterAndViewDistance(czmViewer, boundingSphere);
        if (!cav) return false;
        const [center, viewDistance] = cav
        const rotation = [...defaultFlyToRotation] as [number, number, number];
        const { position: flyPosition, rotation: flyRotation } = getFlyToCenterAndRotation(center, rotation, viewDistance);
        czmViewer.flyTo(flyPosition, 0, flyRotation, duration && duration * 1000);
        return true;
    }
    createModel(meshPath: string, instances: CzmInstances) {
        const { czmViewer, sceneObject } = this;
        const destroy = new Destroyable();
        const czmModel = destroy.ad(new ESGltfModel());
        this.czmViewer.add(czmModel);
        destroy.ad(() => czmViewer.delete(czmModel));

        this._modelArr[meshPath] = czmModel;

        czmModel.instances = instances;
        czmModel.url = meshPath;
        czmModel.allowPicking = true
        destroy.ad(track([czmModel, 'show'], [sceneObject, 'show']));
        destroy.ad(
            czmModel.pickedEvent.don(pickedInfo => {
                if ((sceneObject.allowPicking ?? false)) {
                    sceneObject.pickedEvent.emit(new SceneObjectPickedInfo(sceneObject, pickedInfo));
                }
            })
        )
        return destroy;
    }
    createEditModelProcess() {
        const { czmViewer } = this;
        return this.ad(createProcessingFromAsyncFunc(async (cancelsManager, treeId: string, editingMode: "Position" | "Rotation") => {
            const destroy = cancelsManager.disposer;
            const treesIndex = this.getTreesIndex([treeId])
            // 查找对应模型相关信息
            for (const meshPath in this._modelArr) {
                const model = this._modelArr[meshPath]
                if (!model.instances) continue;
                const index = model.instances.findIndex(instance => instance.id === treesIndex[0]);
                if (index != -1 && model.instances) {
                    // 隐藏对应模型，创建临时模型替换
                    const czmModel = czmViewer.getCzmObject((czmViewer.getCzmObject(model) as CzmESGltfModel).model) as CzmCzmModelPrimitive;
                    //@ts-ignore
                    const feature = czmModel.primitive && czmModel.primitive.featureTables[0].getFeature(index);
                    feature && (feature.show = false);
                    const instance = model.instances[index];
                    const { position, rotation, scale } = instance;
                    const tempMode = destroy.ad(new ESGltfModel())
                    czmViewer.add(tempMode);
                    destroy.ad(() => czmViewer.delete(tempMode));
                    tempMode.url = meshPath;
                    tempMode.position = position;
                    tempMode.rotation = rotation ?? [0, 0, 0];
                    tempMode.scale = scale ?? [1, 1, 1];
                    // 修改编辑默认值
                    const czmTempModel = czmViewer.getCzmObject(tempMode) as CzmESGltfModel;
                    tempMode.editing = true;
                    czmTempModel.sPrsEditing.prsEditing && (czmTempModel.sPrsEditing.prsEditing.editingMode = editingMode);
                    await step(cancelsManager, cancelsManager => {
                        return new Promise<void>((resolve, reject) => {
                            const destroy = cancelsManager.disposer;
                            destroy.ad(reject);
                            // 编辑结束，返回
                            destroy.ad(tempMode.editingChanged.don(() => resolve()))
                        })
                    })
                    // 编辑结束，删除临时模型，更新模型
                    instance.position = tempMode.position;
                    instance.rotation = tempMode.rotation;
                    instance.scale = tempMode.scale;
                    const temp = [...model.instances];
                    model.instances = [];
                    model.instances = temp;
                    return;
                }
            }
        }))
    }
    highLightModel(treesIndex: number[]) {
        const { czmViewer } = this;
        // 清空上次颜色
        this._highLightFeature.forEach(feature => {
            feature.color = toColor([1, 1, 1, 1]);
        });
        this._highLightFeature = [];
        for (const meshPath in this._modelArr) {
            const model = this._modelArr[meshPath]
            treesIndex.forEach(treeIndex => {
                if (!model.instances) return;
                const index = model.instances.findIndex(instance => instance.id === treeIndex);
                if (index !== -1) {
                    // 更改颜色
                    const czmModel = czmViewer.getCzmObject((czmViewer.getCzmObject(model) as CzmESGltfModel).model) as CzmCzmModelPrimitive;
                    if (czmModel.primitive) {
                        //@ts-ignore
                        const feature = czmModel.primitive.featureTables[0].getFeature(index)
                        this._highLightFeature.push(feature);
                        feature.color = toColor([0, 1, 0, 1])
                    }
                }
            })
        }
    }
    getTreeArr(trees: newESJTreeParam[]) {
        const tempJson = {} as { [meshPath: string]: CzmInstances };
        for (let i = 0; i < trees.length; i++) {
            const element = trees[i];
            const meshPath = this.sceneObject.treeTypesCesium[element.treeTypeId].meshPath;
            if (!Reflect.has(tempJson, meshPath))
                tempJson[meshPath] = []
            tempJson[meshPath].push({
                id: i,
                position: element.location,
                rotation: element.rotation,
                scale: element.scale,
            });
        }
        return tempJson;
    }
    getTreesIndex(treeIds: string[] | newESJTreeParam[]) {
        const temp = [] as number[];
        for (let i = 0; i < treeIds.length; i++) {
            const element = treeIds[i];
            const index = this.sceneObject.trees.findIndex(tree => typeof element != 'string' ? tree.id === element.id : tree.id === element);
            if (index !== -1) {
                temp.push(index);
            }
        }
        return temp;
    }
}